package br.com.massai.view.utils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileDeleteStrategy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import br.com.massai.model.exceptions.ErrorCore;
import br.com.massai.model.exceptions.SystemException;

@SuppressWarnings({ "all" })
public class FileUtil {
	
	private static final Logger logger = LogManager.getLogger(FileUtil.class);
	private static final String ARQUIVO_JA_EXISTENTE = "Arquivo já existente";

	private FileUtil() { }

	/**
	 * Cria um arquivo novo com os dados informados.
	 * 
	 * @param path
	 *      O caminho, incluindo o nome do arquivo.
	 * @param dados
	 *      Os dados que serao gravados no arquivo.
	 * @throws SystemException
	 * 		Caso ocorra alguma exceção ao criar o arquivo ou ao gravar os dados no mesmo.
	 * 
	 */
	public static void criar(String path, byte[] dados) throws SystemException {
		File arquivo = criar(path, false);

		try {
			salvar(arquivo, dados);

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("criar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}
	}

	/**
	 * Cria um arquivo novo com os dados informados.
	 * 
	 * @param path
	 *            O caminho, incluindo o nome do arquivo.
	 * @param dados
	 *            Os dados que serao gravados no arquivo.
	 * @param sobrescreve
	 *            Parametro passado informando se o arquivo, caso exista, sera
	 *            sobrescrito
	 * @throws SystemException
	 *             Caso ocorra alguma exceção ao criar o arquivo ou ao gravar os
	 *             dados no mesmo.
	 */
	public static void criar(String path, byte[] dados, boolean sobrescreve)
			throws SystemException {

		File arquivo = criar(path, sobrescreve);

		try {

			salvar(arquivo, dados);

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("criar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}
	}

	/**
	 * Cria um arquivo novo com os dados informados.
	 * 
	 * @param path
	 *            o caminho, incluindo o nome do arquivo.
	 * @param linhas
	 *            uma coleção de String, onde cada String eh uma linha no
	 *            arquivo.
	 * @throws SystemException
	 *             caso ocorra alguma exceção ao criar o arquivo ou ao gravar os
	 *             dados no mesmo
	 */
	public static void criar(String path, Collection<String> linhas)
			throws SystemException {

		File arquivo = criar(path, false);

		try {

			salvar(arquivo, linhas);

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("criar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}
	}

	/**
	 * Cria um arquivo novo com os dados informados.
	 * 
	 * @param path
	 *            O caminho, incluindo o nome do arquivo.
	 * @param linhas
	 *            Uma coleção de String, onde cada String eh uma linha no
	 *            arquivo.
	 * @param sobrescreve
	 *            Parametro passado informando se o arquivo, caso exista, sera
	 *            sobrescrito
	 * @throws SystemException
	 *             Caso ocorra alguma exceção ao criar o arquivo ou ao gravar os
	 *             dados no mesmo.
	 */
	public static void criar(String path, Collection<String> linhas,
			boolean sobrescreve) throws SystemException {

		File arquivo = criar(path, sobrescreve);

		try {

			salvar(arquivo, linhas);

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("criar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}
	}

	/**
	 * Salva os bytes informados no arquivo passado.
	 * 
	 * @param arquivo
	 *            O arquivo onde sera gravado os bytes
	 * @param dados
	 *            Os dados que devem ser gravados.
	 * @throws SystemException
	 *             Caso ocorra alguma exceção ao gravar os dados.
	 */
	public static void salvar(File arquivo, byte[] dados) throws SystemException {

		try {

			OutputStream os = new FileOutputStream(arquivo);
			os.write(dados);
			os.flush();
			os.close();

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("salvar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

	}

	/**
	 * Salva os bytes informados no arquivo passado.
	 * 
	 * @param arquivo
	 *            O arquivo onde sera gravado os bytes
	 * @param c
	 *            Os dados que devem ser gravados.
	 * @throws SystemException
	 *             Caso ocorra alguma exceção ao gravar os dados.
	 */
	public static void salvar(File arquivo, Collection c) throws SystemException {

		try {

			Iterator it = c.iterator();
			String str = null;

			FileWriter writer = new FileWriter(arquivo);
			BufferedWriter buffer = new BufferedWriter(writer);

			while (it.hasNext()) {
				str = (String) it.next();

				buffer.write(str);

				if (it.hasNext()) {
					buffer.newLine();
				}
			}

			buffer.flush();
			buffer.close();

		} catch (SecurityException e) {
			logger.error(gerarMensagemErroAcesso("salvar", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

	}

	/**
	 * Exclui um arquivo denotado pelo path contido no File informado. Se o o
	 * path indicar um diretório, este precisa estar vazio para ser excluido.
	 * 
	 * @param arquivo
	 *            o arquivo/diretório a ser excluído
	 * @return <code>true</code> Se o arquivo ou diretório foi excluido com
	 *         sucesso; <code>false</code> caso contrário.
	 * @throws SystemException
	 *             Se ocorrer algum erro de permissão para exclusão do arquivo
	 */
	public static void excluir(File arquivo) throws SystemException {

		try {
			System.gc();
			FileDeleteStrategy.FORCE.delete(arquivo);
		} catch (Exception e) {
			logger.error(gerarMensagemErroAcesso("excluir", arquivo), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

	}

	/**
	 * Exclui um arquivo denotado pelo path informado. Se o o path indicar um
	 * diretório, este precisa estar vazio para ser excluido.
	 * 
	 * @param pathName
	 *            o path do arquivo/diretório a ser excluido
	 * @return <code>true</code> Se o arquivo ou diretório foi excluido com
	 *         sucesso; <code>false</code> caso contrário.
	 * @throws SystemException
	 *             Se ocorrer algum erro de permissão para exclusão do arquivo
	 */
	public static void excluir(String pathName) throws SystemException {
		File arquivo = new File(pathName);
		excluir(arquivo);
	}

	/**
	 * Retorna a mensagem de permissão negada ao arquivo.
	 * 
	 * @param acao
	 *            a ação (leitura/escrita) cuja execução foi negada
	 * @param arquivo
	 *            o arquivo
	 * @return a mensagem de permissão negada ao arquivo.
	 */
	private static String gerarMensagemErroAcesso(String acao, File arquivo) {

		StringBuffer sb = new StringBuffer();
		sb.append("Permissão negada ao tentar ");
		sb.append(acao);
		sb.append(" o arquivo ");
		sb.append(arquivo.getPath());

		return sb.toString();
	}

	/**
	 * Cria um arquivo novo a partir do path informado.
	 * 
	 * @param path
	 *            o path informado
	 * @param sobrescreve
	 *            um boolean indicando sobrescrita de arquivo
	 * @return um arquivo novo a partir do path informado
	 * @throws SystemException
	 *             caso o arquivo já exista ou caso ocorra algum problema na
	 *             criação do mesmo
	 */
	private static File criar(String path, boolean sobrescreve)
			throws SystemException {

		File arquivo = null;

		try {

			arquivo = new File(path);
			boolean ehNovo = arquivo.createNewFile();

			if (!ehNovo && !sobrescreve) {
				throw new SystemException(ErrorCore.ERROR_SYSTEM);
			}

		} catch (SystemException e) {
			logger.error(ARQUIVO_JA_EXISTENTE, e);
			throw e;
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

		return arquivo;
	}

	/**
	 * método para retornar o array de bytes de um arquivo.
	 * 
	 * @param path
	 *            O caminho de um arquivo
	 * @return O array de bytes de um arquivo
	 * @throws SystemException
	 *             caso ocorra algum erro no sistema
	 */
	public static byte[] obterBytes(String path) throws SystemException {

		byte[] conteudo = null;

		try {
			File file = new File(path);
			FileInputStream is = new FileInputStream(file);
			long length = file.length();
			conteudo = new byte[(int) length];

			int offset = 0;
			int numRead = 0;

			while (offset < conteudo.length
					&& (numRead = is.read(conteudo, offset, conteudo.length
							- offset)) >= 0) {
				offset += numRead;
			}

		} catch (FileNotFoundException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}
		return conteudo;
	}

	/**
	 * Retorna uma coleção de String com o conteúdo das linhas do arquivo.
	 * 
	 * @param arquivo
	 *            Um arquivo texto.
	 * @return uma coleção de String com o conteúdo das linhas do arquivo.
	 * @throws SystemException
	 *             caso ocorra alguma exceção
	 */
	public static Collection<String> obterLinhas(File arquivo)
			throws SystemException {

		Collection<String> linhas = new ArrayList<String>();

		try {

			FileReader reader = new FileReader(arquivo);
			BufferedReader buffer = new BufferedReader(reader);

			String linha = null;

			while (buffer.ready()) {
				linha = buffer.readLine();
				linhas.add(linha);
			}

		} catch (FileNotFoundException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

		return linhas;
	}

	/**
	 * Retorna uma coleção de String com o conteúdo das linhas do arquivo
	 * excluindo-se as linhas em branco.
	 * 
	 * @param arquivo
	 *            Um arquivo texto.
	 * @return uma coleção de String com o conteúdo das linhas do arquivo.
	 * @throws SystemException
	 *             caso ocorra alguma exceção
	 */
	public static Collection<String> obterLinhasComDados(File arquivo)
			throws SystemException {

		Collection<String> linhas = new ArrayList<String>();

		try {

			FileReader reader = new FileReader(arquivo);
			BufferedReader buffer = new BufferedReader(reader);

			String linha = null;

			while (buffer.ready()) {
				linha = buffer.readLine();
				if (!StringUtil.ehBrancoOuNulo(linha.trim())) {
					linhas.add(linha);
				}
			}

		} catch (FileNotFoundException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(ErrorCore.ERROR_SYSTEM, e);
		}

		return linhas;
	}

	/**
	 * Move o arquivo para um determinado diretório, sobrescrevendo se o arquivo
	 * já existir.
	 * 
	 * @param arquivo
	 *            o arquivo a ser movido
	 * @param diretorioDestino
	 *            o diretório para o qual o arquivo sera movido
	 * @return o arquivo movido
	 * @throws Exception
	 *             caso ocorra alguma exceção
	 */
	public static File moverArquivo(File arquivo, String diretorioDestino)
			throws Exception {

		File dirDestino = new File(diretorioDestino);
		dirDestino.mkdirs();
		File novoArquivo = new File(diretorioDestino, arquivo.getName());

		if (novoArquivo.exists()) {
			novoArquivo.delete();
		}

		if (!arquivo.renameTo(novoArquivo)) {
			if (!copiar(arquivo, novoArquivo)) {
				logger.error("Não foi possível mover o arquivo.");
				throw new Exception("não foi possível mover o arquivo.");
			} else {
				arquivo.delete();
			}

		}

		return novoArquivo;
	}

	/**
	 * Copia arquivo desejado, para o arquivo de destino Se o arquivo de destino
	 * não existir, ele sera criado
	 * 
	 * @throws SystemException
	 */
	public static boolean copiar(File arquivo, File copia) throws SystemException {

		boolean retorno = true;

		try {
			InputStream in = new FileInputStream(arquivo);
			OutputStream out = new FileOutputStream(copia);

			// Transferindo bytes de entrada para saída
			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}

			in.close();
			out.close();

		} catch (IOException e) {
			logger.error("Error", e);
			retorno = false;
			throw new SystemException(ErrorCore.ERROR_COPY_FILE, e);
		}

		return retorno;

	}

	/**
	 * Executar download de arquivo
	 * 
	 * @param fullFileName
	 * @param nomeExibicao
	 */
	public static synchronized void downloadFile(String fullFileName,
			String nomeExibicao) throws SystemException {
		File file = new File(fullFileName);
		HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
		response.setHeader("Expires", "0");
		response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
		response.setHeader("Pragma", "public");
		// response.addHeader("Content-Type", "application/force-download");
		response.setHeader("Content-Disposition", new StringBuilder().append("attachment;filename=\"").append(nomeExibicao).append("\"").toString());
		response.setContentLength((int) file.length());
		response.setContentType("application/x-msdownload");
		try {
			FileInputStream in = new FileInputStream(file);
			OutputStream out = response.getOutputStream();

			byte[] buf = new byte[(int) file.length()];
			int count;
			while ((count = in.read(buf)) >= 0) {
				out.write(buf, 0, count);
			}
			in.close();
			out.flush();
			out.close();
			FacesContext.getCurrentInstance().responseComplete();
		} catch (FileNotFoundException e) {
			throw new SystemException(ErrorCore.ERROR_LOAD_FILE, e);
		} catch (IOException e) {
			throw new SystemException(ErrorCore.ERROR_LOAD_FILE, e);
		}
	}

	/**
	 * 
	 * @param file
	 * @throws IOException
	 */
	public static synchronized void download(File file) throws IOException {

		HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
		response.setHeader("Content-Disposition", new StringBuilder().append("attachment;filename=\"").append(file.getName()).append("\"").toString());
		response.setContentLength((int) file.length());

		InputStream in = new FileInputStream(file);
		PrintWriter output = response.getWriter();

		int bit = 256;
		try {
			while ((bit) >= 0) {
				bit = in.read();
				output.write(bit);
			}

		} catch (IOException e) {
			e.printStackTrace(System.out);
		}
		output.flush();
		output.close();
		in.close();
	}

	/**
	 * Descompacta um arquivo no formato ZIP
	 * 
	 * @param zipFile
	 * @param diretorioDestino
	 * @throws SystemException
	 */
	public static void descompactarArquivoZIP(String caminhoZipFile, String caminhoDiretorioDestino) throws SystemException {
		File zipFile = new File(caminhoZipFile);
		File diretorioDestino = new File(caminhoDiretorioDestino);
		ZipFile zip = null;
		File arquivo = null;
		InputStream is = null;
		OutputStream os = null;
		byte[] buffer = new byte[1024];

		try {
			// cria diretório informado, caso não exista
			if (!diretorioDestino.exists()) {
				diretorioDestino.mkdirs();
			}

			if (!diretorioDestino.exists() || !diretorioDestino.isDirectory()) {
				throw new SystemException(
						ErrorCore.ERROR_UNZIP_FILE,
						new StringBuilder().append("O diretório ")
								.append(diretorioDestino.getName())
								.append(" é inválido.").toString());
			}

			zip = new ZipFile(zipFile);
			Enumeration e = zip.entries();
			while (e.hasMoreElements()) {
				ZipEntry entrada = (ZipEntry) e.nextElement();
				arquivo = new File(diretorioDestino, entrada.getName());

				// se for diretório inexistente, cria a estrutura e pula
				// pra próxima entrada
				if (entrada.isDirectory() && !arquivo.exists()) {
					arquivo.mkdirs();
					continue;
				}

				// se a estrutura de diretórios não existe, cria
				if (!arquivo.getParentFile().exists()) {
					arquivo.getParentFile().mkdirs();
				}
				// lê o arquivo do zip e grava em disco
				is = zip.getInputStream(entrada);
				os = new FileOutputStream(arquivo);
				int bytesLidos = 0;
				if (is == null) {
					throw new SystemException(
							ErrorCore.ERROR_UNZIP_FILE,
							new StringBuilder()
									.append("Impossível ler a entrada do zip: ")
									.append(entrada.getName()).toString());
				}
				while ((bytesLidos = is.read(buffer)) > 0) {
					os.write(buffer, 0, bytesLidos);
				}
			}
		} catch (Exception e) {
			StringBuilder message = new StringBuilder(e.getMessage());
			if (e.getMessage().contains("MALFORMED")) {
				message.append("Caracteres especiais nos nomes dos arquivos. ")
						.append(message);
			}
			throw new SystemException(ErrorCore.ERROR_UNZIP_FILE,
					message.toString());

		} finally {
			try {
				if (is != null) {
					is.close();
				}
				if (os != null) {
					os.close();
				}
				if (zip != null) {
					zip.close();
				}
			} catch (IOException e) {
				logger.error("Ocorreu um erro ao tentar fechar o arquivo ZIP",
						e);
			}
		}
	}

}
